摘要
這篇文章探討了 LangGraph 這個框架,它用於提升 AI 助理的記憶能力,並能有效管理對話歷史。文章首先說明了 AI 助理需要記憶功能的原因,以及記憶功能如何改善使用者體驗。接著文章深入解析 LangGraph 的核心機制,Checkpointer,它是一個狀態保存器,能捕捉並儲存圖形的完整狀態,就像 AI 的記憶儲存器。文章也介紹了三種不同的記憶類型:對話歷史、知識庫和語義緩存,並深入探討它們的技術實現方式。文章也說明了如何有效管理對話歷史,透過過濾消息和修剪消息技術,可以精確控制傳遞給 AI 模型的上下文信息,避免性能問題,並確保回應的相關性和準確性。最後,文章總結了使用 LangGraph 來提升 AI 助理記憶能力和對話管理能力的關鍵優點。
開始之前,我想先問看看,為什麼需要替 Agent 添加記憶能力?
想像一下,如果你每次跟朋友聊天,他都忘記你之前說過的話,那會是什麼感覺?超級尷尬吧!AI 助理也一樣,沒有記憶的話,每次對話都像是從頭開始。但是,具體來說,有記憶的 AI 助理能做什麼特別的事情嗎?舉個例子吧:假設你告訴 AI 助理你對花生過敏。有了記憶,下次你問「今天午餐吃什麼好?」它就會避免推薦花生相關的食物。
那麼,給 AI 加上記憶功能,最大的好處是什麼呢?
你有沒有試過要完成一個複雜的專案?像是籌備婚禮或是裝修房子?有記憶的 AI 助理可以幫你記住所有細節,跟進每個任務,甚至根據過去的經驗給你建議。它就像是你的超級助理,隨時隨地幫你分擔腦力,讓你不會漏掉任何重要的事情。這樣一來,你就可以把精力集中在真正重要的事情上,是不是很棒?
你有沒有想過,如果 AI 助理真的能夠「記住」我們的對話,會是什麼樣子?讓我們一起來看看,在日常生活中,AI 是如何運用不同類型的「記憶」來幫助我們的。
想像一下這樣的場景:
小美:「嗨,我想計劃一趟歐洲旅行。」
AI:「你好小美!記得你上次提到想看北極光,要將北歐納入行程嗎?」
小美:「對,不過我也想去溫暖的地方。」
AI:「明白了。考慮到你的喜好和現在是夏天,建議你可以先去義大利,然後 10 月去挪威看北極光。義大利有豐富的藝術和美食,符合你的興趣。挪威的特羅姆瑟是觀賞北極光的好地方。根據你之前提到的預算,這趟旅行大約需要 3000-4000 歐元,不包括機票。需要我幫你規劃詳細行程嗎?」
小美:「聽起來不錯!謝謝你的建議。」
看完這個對話,你是否好奇 AI 是如何做到既了解小美的需求,又能提供專業建議的呢?讓我們來解析一下 AI 在這段對話中運用的三種不同「記憶」類型:
對話歷史(Conversation History):記錄 AI 系統與使用者之間的歷史對話內容,使 AI 能夠回顧先前的交互,保持對話的連貫性。
知識庫(Knowledge Base):儲存大量結構化的事實和資訊,提供 AI 回答問題和做出決策所需的基礎知識。
語義緩存(Semantic Memory Buffer):短期儲存並處理複雜概念的機制,使 AI 能夠在當前對話中進行多步驟推理和概念關聯。
現在,讓我們來解析一下 AI 在這段對話中如何運用這三種「記憶」類型:
對於那些對技術細節感興趣的讀者,以下是每種記憶類型的簡要技術說明:
在 AI 應用開發過程中,跨多次互動共享上下文的能力至關重要。LangGraph 透過 Checkpointer 為所有 StateGraph 提供了這項關鍵功能。接下來我們將深入探討這個機制的運作原理。
Checkpointer 的本質
Checkpointer 本質上是一個狀態保存器。它的主要職責是在 StateGraph 執行過程中,於特定時間點捕捉並儲存圖的完整狀態。這個過程確保了系統狀態的完整保存。
實作方式
在 LangGraph 中實作 Checkpointer 相當直觀:
compile(checkpointer=custom_checkpointer)
my_checkpointer = CustomCheckpointer()
graph.compile(checkpointer=my_checkpointer)
Checkpointer 的設計具有高度的靈活性,支援多種後端儲存方案。這包括從簡單的記憶體存儲到更複雜的資料庫系統,如 SQLite。這種多樣性使開發者能夠根據應用的具體需求選擇最適合的儲存方式。
雖然 Checkpointer 支援多種後端儲存方案,包括記憶體存儲和 SQLite,但對於更大規模或需要更複雜查詢功能的應用,外部資料庫如 MongoDB 可能是更好的選擇。我們將在下一章節深入探討這個話題。
LangGraph 中的狀態保存過程
在 LangGraph 中,狀態保存是通過內建的持久層來實現的。這個機制允許在執行過程中的不同時間點儲存圖形的狀態。以下是狀態保存過程的關鍵要素:
每當 LangGraph 完成一次狀態更新,也就是圖中所有節點都處理完自己的工作並更新狀態後,檢查點儲存器就會立即執行,將最新的狀態保存下來。這種方式確保了在執行過程中的每個重要階段,都能保存圖形的完整狀態。
checkpointer 就像是使用者的歷史紀錄,會幫你存檔對話進度,下次聊天直接讀檔繼續,不用重頭來過喔。
在 LangGraph 的架構中,「狀態」和「檢查點」是兩個核心但截然不同的概念。讓我們深入探討它們的特性及主要差異:
狀態代表應用程式在任一給定時刻的當前快照。它是一個動態數據結構,隨著圖形的執行持續更新。狀態通常具備以下特徵:
檢查點則是一種持久化機制,用於保存和恢復圖形的狀態。檢查點的主要功能包含:
實作 Checkpoint 機制時,我們有幾種不同的選擇,每種方法都有其特定的應用場景和優點。目前主要有以下幾種方式:
那麼,我們要如何在實際應用中有效地管理聊天歷史紀錄呢?
在本章節中,我們會專注於使用記憶體保存方式來示範 Checkpointer 的應用,特別是在管理聊天歷史紀錄方面。此外,我們還會探討兩種 LangChain 機制,這些機制在處理大量聊天訊息時特別有效。透過這種方式,我們不僅能夠了解 Checkpointer 的基本操作,還能學習如何在面對資料量增加時維持系統的效能和穩定性。
利用記憶體來儲存圖狀態的方法,使用字典結構來管理資料,這種儲存方式的訪問和檢索速度非常快,因為它不涉及磁碟 I/O 操作。
我們來編譯圖,並加入記憶功能:
from langgraph.checkpoint.memory import MemorySaver
workflow = StateGraph(MessagesState)
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)
workflow.add_edge(START, "agent")
workflow.add_conditional_edges(
"agent",
should_continue,
)
workflow.add_edge("action", "agent")
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
現在,我們來試試看這個有記憶功能的AI 代理:
看到了嗎?AI 記住了小明的名字!但如果我們用不同的 thread_id,AI 就會忘記之前的對話喔。
在 LangGraph 系統中,thread_id
是一個與特定執行緒相關聯的唯一識別符。每個執行緒代表一個獨立的互動或對話流程,使系統能夠同時管理多個對話或處理程序。
thread_id 的定義與應用:
thread_id 是由檢查點儲存器(checkpointer)指派給一系列檢查點的唯一識別碼。
在使用檢查點儲存器時,執行圖形時必須指定 thread_id 或 checkpoint_id。
注意:根據 LangGraph v0.2 的官方文件,原先的 thread_ts 和 parent_ts 已分別更名為 checkpoint_id 和 parent_checkpoint_id。
隨著對話越來越長,歷史記錄會不斷累積,可能會讓 AI 的反應變慢,甚至出錯。怎麼辦呢?我們有兩個好方法:
在複雜的鏈式結構和代理中,我們可能會使用訊息列表來追蹤狀態。消息過濾技術允許我們從複雜的對話歷史中提取最相關的資訊。通過 LangChain 的 filter_messages
函數,我們可以基於多種條件選擇性地保留或排除特定消息:
官方文件
我們先來看看基礎用法,單純用 LangChain
messages = [
SystemMessage("你是一個優秀的助理。"),
HumanMessage("你叫什麼名字?", id="q1", name="台灣使用者"),
AIMessage("我叫小智。", id="a1", name="AI助理"),
HumanMessage("你最喜歡的台灣小吃是什麼?", id="q2"),
AIMessage("我最喜歡的是臺灣的珍珠奶茶!", id="a2"),
]
filtered_msgs = filter_messages(
messages,
include_names=("台灣使用者", "AI助理"),
include_types=("system",),
exclude_ids=("a1",),
)
print(filtered_msgs)
這個例子展示了如何過濾訊息,只保留特定的使用者名稱、訊息類型,並排除特定 ID 的訊息。
管理對話歷史的另一個重要概念是限制傳遞給模型的訊息數量。LangChain 提供了一些內建的輔助函數來管理訊息列表。在這裡,我們將使用 trim_messages
輔助函數來減少傳送給模型的訊息數量。
trim_messages
是 LangChain 提供的一個實用函數,用於管理和限制傳遞給語言模型的訊息數量。其主要目的是確保對話歷史不會超出模型的上下文視窗限制。讓我們深入了解它的運作機制:
trim_messages
的核心思想是根據指定的標準(如最大令牌數)來修剪訊息列表。
trimmer = trim_messages(
max_tokens=100,
strategy="last",
token_counter=model,
include_system=True, # 保留初始的系統訊息
allow_partial=False, # 不允許分割訊息內容
start_on="human", # 確保第一條訊息(不包括系統訊息)始終是特定類型
)
修剪過程如下:
保留系統訊息:
由於 include_system=True
,首先會保留系統訊息:SystemMessage(content="你是一個了解台灣文化的助理")
反轉訊息列表:
因為 strategy="last"
,訊息列表會被反轉,以便從最新的訊息開始處理。
開始累加令牌:
allow_partial=False
,這條訊息不會被部分包含。在這個例子中,我們使用了與台灣相關的對話內容,並設置了一個修剪器來限制傳送給模型的訊息數量。這種方法可以有效地管理對話歷史,確保不會超出模型的上下文視窗限制,同時保持對話的連貫性和相關性。
在本文中,我們深入探討了如何利用 LangGraph 和 LangChain 的先進功能來增強 AI 助理的記憶能力和對話管理能力。我們介紹了 Checkpointer 機制,它如同 AI 的記憶儲存器,使 AI 能夠在多次互動中保持上下文的連貫性。我們還探討了不同類型的記憶儲存方案,從簡單的 MemorySaver 到複雜的外部數據庫系統,每種方案都有其特定的應用場景。
特別值得注意的是,我們深入研究了如何有效管理對話歷史。通過消息過濾和修剪技術,我們可以精確控制傳遞給 AI 模型的上下文信息,既確保了回應的相關性和準確性,又避免了因上下文過長而導致的性能問題。
這些技術的結合不僅提高了 AI 助理的智能水平,還大大增強了其實用性和效率。通過實施這些方法,我們可以創建出更加智能、更具個性化,且能夠持續學習和適應的 AI 系統。
即刻前往教學程式碼 Repo,親自動手提供 AI 代理額外記憶能力吧!別忘了給專案按個星星並持續關注更新,讓我們一起探索AI代理的新境界。
X. 參考資料
現在 LangGraph 已經在官方文件補充上 Memory 細節
分成 Checkpoint 以及 store
https://langchain-ai.github.io/langgraph/concepts/memory/#what-is-memory
如果想知道未來 LangGraph 還會如何發展 Memory 的話,可以參考【Day 9】- 從 Redux 到 LangGraph:AI 時代下的狀態管理新思維 使用第一性原理來看待